#include "miscellaneous.h"
#include "FrameGLFW.h"
#include <mutex>
#include <thread>
#include <future>
#include <GL/glew.h>
#include <GLFW/glfw3.h>

#include "Object.h"
#include "Canvas.h"
#include "Renderer.h"

bool FrameGLFW::_initialized = false;

FrameGLFW::FrameGLFW()
{
    _title = "Simple Ray Tracer";
}

FrameGLFW::~FrameGLFW()
{

}

void FrameGLFW::Initialize()
{
    if (_initialized)
        return ;

    auto glfwError = glfwInit();
    if (GLFW_TRUE != glfwError) {
        CPrintLine("GLFW init ERROR!", enmCFC_Red);
        return ;
    }

    //glfwSwapInterval(0);
    glfwWindowHint(GLFW_SAMPLES, 4);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

    GLFWwindow* wnd = glfwCreateWindow(64, 64, "initialize window", nullptr, nullptr);
    if (nullptr == wnd) {
        CPrintLine("Create GLFW Window Failed!", enmCFC_Red);
        glfwTerminate();
        return ;
    }

    glfwMakeContextCurrent(wnd);

    auto glewError = glewInit();
    if (GLEW_OK != glewError) {
        CPrintLine("GLEW init ERROR!", enmCFC_Red);
        glfwTerminate();
        return ;
    }

    glfwDestroyWindow(wnd);

    glfwMakeContextCurrent(nullptr);

    _initialized = true;
}

void FrameGLFW::Destory()
{
    _initialized = false;
    glfwMakeContextCurrent(nullptr);
    glfwTerminate();
}

bool FrameGLFW::initWindow(int x, int y, int w, int h)
{
    if (!_initialized)
        return false;

    clean();

    _wnd = glfwCreateWindow(w, h, _title.c_str(), nullptr, nullptr);
    if (nullptr == _wnd) {
        CPrintLine("Create GLFW Window Failed!", enmCFC_Red);
        return false;
    }

    glfwMakeContextCurrent(_wnd);

    glfwSetWindowUserPointer(_wnd, this);
    glfwSetErrorCallback(Error);
    glfwSetWindowSizeCallback(_wnd, Resize);
    glfwSetWindowCloseCallback(_wnd, Close);
    glfwSetMouseButtonCallback(_wnd, Mouse);
    glfwSetCursorPosCallback(_wnd, Cursor);
    glfwSetScrollCallback(_wnd, Scroll);
    glfwSetKeyCallback(_wnd, Keyboard);
    glfwSetDropCallback(_wnd, Drop);

    _canvas = new Canvas;
    _renderer = new Renderer;

    _canvas->init(w, h);

    _renderer->setViewport(0, 0, w, h);
    _renderer->setLookAt(vec3(0, 0, 0), vec3(0, 0, -1), vec3(0, 1, 0));
    _renderer->setPerspective(45.0, w, h, 0.01, 10);

    ref_ptr<Sphere> obj1 = new Sphere(vec3(0.3, 0, -1.7), 0.3);
    obj1->color() = vec4(1, 0, 0, 1);
    _renderer->addObject(obj1);

    ref_ptr<Sphere> obj2 = new Sphere(vec3(-0.3, 0, -1.5), 0.2);
    obj2->color() = vec4(1, 1, 0, 1);
    _renderer->addObject(obj2);

    return true;
}

void FrameGLFW::run()
{
    //auto fut = std::async(std::launch::async, [this](){
    //    while (!glfwWindowShouldClose(_wnd)) {
    //        _renderer->render(_canvas->width(), _canvas->height(), _canvas->bytesPerPixel(), _canvas->raw());
    //        _canvas->dirty();
    //    }
    //});

    Timer timer;
    char title[256];
    while (!glfwWindowShouldClose(_wnd))
    {
        timer.start();
        _renderer->render(_canvas->width(), _canvas->height(), _canvas->bytesPerPixel(), _canvas->raw());
        _canvas->dirty();
        _canvas->render();

        glfwSwapBuffers(_wnd);

        glfwPollEvents();

        timer.end();

        sprintf(title, "%s : %.1f", _title.c_str(), 1.0 / timer.time_s());
        glfwSetWindowTitle(_wnd, title);
    }
}

void FrameGLFW::clean() 
{
    if (_renderer.valid())
        _renderer->clearAll();

    if (_wnd) 
    {
        glfwMakeContextCurrent(_wnd);
        
        if (_canvas.valid())
            _canvas->release();
        
        glfwDestroyWindow(_wnd);
        _wnd = nullptr;
    }
}

void FrameGLFW::resize(int w, int h) 
{
    _canvas->resize(w, h);
    _renderer->setViewport(0, 0, w, h);
    _renderer->setPerspective(45, w, h, 0.01, 10);
}

void FrameGLFW::close() 
{
    _renderer->clearAll();
    _canvas->release();
}

void FrameGLFW::mouse(int button, int action, int mods)
{
    // TODO: mouse
}

void FrameGLFW::cursor(double xpos, double ypos) 
{
    // TODO: cursor
}

void FrameGLFW::scroll(double xoffset, double yoffset) 
{
    // TODO: scroll
}

void FrameGLFW::keyboard(int key, int scancode, int action, int mods) 
{
    // TODO: keyboard
}

void FrameGLFW::drop(int count, const char** paths) 
{
    // TODO: drop
}

void FrameGLFW::Error(int errCode, const char* errMsg) 
{
    CPrintf(enmCFC_Yellow, enmCBC_Black, "[FrameGLFW]%d : %s\n", errCode, errMsg);
}

void FrameGLFW::Resize(GLFWwindow* wnd, int w, int h)
{
    FrameGLFW* pointer = (FrameGLFW*)glfwGetWindowUserPointer(wnd);
    if(pointer) pointer->resize(w, h);
}

void FrameGLFW::Close(GLFWwindow* wnd)
{
    FrameGLFW* pointer = (FrameGLFW*)glfwGetWindowUserPointer(wnd);
    if (pointer) pointer->close();
}

void FrameGLFW::Mouse(GLFWwindow* wnd, int button, int action, int mods)
{
    FrameGLFW* pointer = (FrameGLFW*)glfwGetWindowUserPointer(wnd);
    if (pointer) pointer->mouse(button, action, mods);
}

void FrameGLFW::Cursor(GLFWwindow* wnd, double xpos, double ypos)
{
    FrameGLFW* pointer = (FrameGLFW*)glfwGetWindowUserPointer(wnd);
    if (pointer) pointer->cursor(xpos, ypos);
}

void FrameGLFW::Scroll(GLFWwindow* wnd, double xoffset, double yoffset)
{
    FrameGLFW* pointer = (FrameGLFW*)glfwGetWindowUserPointer(wnd);
    if (pointer) pointer->scroll(xoffset, yoffset);
}

void FrameGLFW::Keyboard(GLFWwindow* wnd, int key, int scancode, int action, int mods)
{
    FrameGLFW* pointer = (FrameGLFW*)glfwGetWindowUserPointer(wnd);
    if (pointer) pointer->keyboard(key, scancode, action, mods);
}

void FrameGLFW::Drop(GLFWwindow* wnd, int count, const char** paths)
{
    FrameGLFW* pointer = (FrameGLFW*)glfwGetWindowUserPointer(wnd);
    if (pointer) pointer->drop(count, paths);
}
